﻿using System;
using System.Linq.Expressions;
using Ast.Framework;
using Ast.Framework.Caching;

namespace Ast.Core.Services
{
    public interface IDynamicValueAccessor : ISingletonDependency
    {
        object GetValue(object obj, string valueAccessPath);
        void SetValue(object obj, string valueAccessPath, object val);
    }

    public class DynamicValueAccessor : IDynamicValueAccessor
    {
        private readonly ICacheManager _cacheManager;

        public DynamicValueAccessor(ICacheManager cacheManager)
        {
            _cacheManager = cacheManager;
        }

        public object GetValue(object obj, string valueAccessPath)
        {
            var expression = BuildGetValueExpression(obj.GetType(), valueAccessPath);
            return expression(obj);
        }

        private Func<object, object> BuildGetValueExpression(Type objType, string valueAccessPath)
        {
            return _cacheManager.Get(string.Format("DynamicValueAccessor.BuildGetValueExpression.{0}.{1}", objType.FullName, valueAccessPath), context =>
            {
                var propertyOrFieldNames = valueAccessPath.Split('.');
                var parameter = Expression.Parameter(typeof (object));
                var parameterConvert = Expression.Convert(parameter, objType);
                var member = Expression.PropertyOrField(parameterConvert, propertyOrFieldNames[0]);
                for (var index = 1; index < propertyOrFieldNames.Length; index++)
                {
                    var propertyOrFieldName = propertyOrFieldNames[index];
                    member = Expression.PropertyOrField(member, propertyOrFieldName);
                }
                var returnConvert = Expression.Convert(member, typeof (object));
                var expression = Expression.Lambda<Func<object, object>>(returnConvert, parameter);
                return expression.Compile();
            });
        }

        public void SetValue(object obj, string valueAccessPath, object val)
        {
            var expression = BuildSetValueExpression(obj.GetType(), valueAccessPath, val.GetType());
            expression(obj, val);
        }

        private Action<object, object> BuildSetValueExpression(Type objType, string valueAccessPath, Type valType)
        {
            return _cacheManager.Get(string.Format("DynamicValueAccessor.BuildSetValueExpression.{0}.{1}", objType.FullName, valueAccessPath), context =>
            {
                var propertyOrFieldNames = valueAccessPath.Split('.');
                var parameter1 = Expression.Parameter(typeof (object));
                var parameter1Convert = Expression.Convert(parameter1, objType);
                var member = Expression.PropertyOrField(parameter1Convert, propertyOrFieldNames[0]);
                for (var index = 1; index < propertyOrFieldNames.Length; index++)
                {
                    var propertyOrFieldName = propertyOrFieldNames[index];
                    member = Expression.PropertyOrField(member, propertyOrFieldName);
                }
                var parameter2 = Expression.Parameter(typeof (object));
                var parameter2Convert = Expression.Convert(parameter2, valType);
                var assign = Expression.Assign(member, parameter2Convert);
                var expression = Expression.Lambda<Action<object, object>>(assign, parameter1, parameter2);
                return expression.Compile();
            });
        }
    }
}